﻿/*
 * Licensed under the MIT license.
 * 
 * Copyright © 2010 Mikko Väänänen
 */

using System;
using System.IO;
using System.Threading;

namespace ShellTail {

	public class FileReader {

		private const int MAX_SEEK_AMOUNT = 1024 * 5;
		private const int LINE_COUNT = 10;
		private const int SLEEP = 1000;

		private string path;
		private IConsoleWriter writer;

		public FileReader(string path, IConsoleWriter writer) {
			this.path = path;
			this.writer = writer;
		}

		public void Read() {

			bool doSeek = true;

			while (true) {
				try {
					using (StreamReader reader = OpenReader(doSeek)) {
						doSeek = false;
						Read(reader);
					}
				} catch (IOException) {
					Thread.Sleep(SLEEP);
				}
			}
		}

		private StreamReader OpenReader(bool doSeek) {
			FileStream fs = OpenStream();
			if (doSeek) {
				SeekToEnd(fs);
			}
			return new StreamReader(fs);
		}

		private FileStream OpenStream() {
			return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
		}

		private void SeekToEnd(FileStream fs) {

			int seekAmount = (int) Math.Min(fs.Length, MAX_SEEK_AMOUNT);

			if (seekAmount > 0) {
				fs.Seek(-seekAmount, SeekOrigin.End);

				byte[] buffer = new byte[seekAmount];
				fs.Read(buffer, 0, seekAmount);
				int position = SearchBackwardsForNewLines(buffer, LINE_COUNT + 1);
				int seekIntoBuffer = buffer.Length - position;

				fs.Seek(-seekIntoBuffer, SeekOrigin.Current);
			}
		}

		private int SearchBackwardsForNewLines(byte[] buffer, int newLineCount) {

			int newLinesFound = 0;

			for (int pos = buffer.Length - 1; pos >= 0; pos--) {
				int c = buffer[pos];
				if (c == '\n') {
					newLinesFound++;
					if (newLinesFound == newLineCount) {
						return pos;
					}
				}
			}

			return 0;
		}

		private void Read(StreamReader reader) {

			long position = 0;

			while (true) {
				string text = reader.ReadToEnd();

				if (text != null && text.Length > 0) {
					position = reader.BaseStream.Position;
					writer.Write(text);
				} else if (GetFileSize() < position) {
					// file was deleted and recreated
					break;
				} else {
					Thread.Sleep(SLEEP);
				}
			}
		}

		private long GetFileSize() {
			FileInfo info = new FileInfo(path);
			return info.Length;
		}
	}
}
